<template>
  <div class='tree-transfer'>
    <div class="left-tree">
      <div class="list">
        <el-tree
          ref="treeRefL"
          v-if="reLoad"
          :data="leftData"
          show-checkbox
          default-expand-all
          :node-key="nodeKey"
          highlight-current
          :props="defaultProps"
        />
      </div>
    </div>
    <div class="btn-div">
      <el-button :icon="Back" type="primary" :disabled="disabled" @click="toLeft()"/>
      <el-button :icon="Right" type="primary" :disabled="disabled" @click="toRight()"/>
    </div>
    <div class="right-tree">
      <div class="list">
        <el-tree
          ref="treeRefR"
          v-if="reLoad"
          :data="rightData"
          show-checkbox
          default-expand-all
          :node-key="nodeKey"
          highlight-current
          :props="defaultProps"
        />
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, nextTick, defineExpose } from 'vue'
import { Right, Back } from '@element-plus/icons-vue';

const props = defineProps({
  nodeKey: String,
  fromData: Array,
  toData: Array,
  defaultProps: {},
  leftTit: String,
  rightTit: String,
  disabled: {
    type: Boolean,
    default: false
  }
})
//定义emit
const emit = defineEmits(['checkVal'])
const treeRefL = ref([])
const treeRefR = ref([])
const leftData = ref([])
const rightData = ref([])
const reLoad = ref(true)

//右侧数据
const toData = ref([])
// 右侧需要移除的数据
const removeData = ref([])

const initData = () => {
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  if (props.toData.length > 0) {
    leftData.value = sortData(originalLeft, props.toData, 'left')
    rightData.value = sortData(originalRight, props.toData, 'right')
  } else {
    leftData.value = originalLeft
    rightData.value = []
  }
}

//方法
//去右边
const toRight = () => {
  // 将勾选中的数据保存到toData中
  const checkNodes = treeRefL.value.getCheckedNodes(false, false)
  const newArr = toData.value.concat(checkNodes)
  const obj = {};
  const peon = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true && cur.push(next);
    return cur;
  }, []) //设置cur默认类型为数组，并且初始值为空的数组
  toData.value = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  // 抽离出选中数据中的id
  const ids = extractId(toData.value)
  // 重新整理两侧树中数据
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
//去左边
const toLeft = () => {
  // 将勾选中的数据保存到toData中
  const checkNodes = treeRefR.value.getCheckedNodes(false, false)

  const newArr = removeData.value.concat(checkNodes)
  const obj = {};
  const peon = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true && cur.push(next);
    return cur;
  }, []) //设置cur默认类型为数组，并且初始值为空的数组
  const dataNeedRemove = peon
  reLoad.value = false
  const originalLeft = JSON.parse(JSON.stringify(props.fromData))
  const originalRight = JSON.parse(JSON.stringify(props.fromData))
  // 抽离出选中数据中的id
  const idsNeedRemove = extractId(dataNeedRemove)
  // 删除相同id
  const oldData = removeId(toData.value, idsNeedRemove)
  toData.value = oldData
  // 右侧列表需要保留的数据的id
  const ids = extractId(oldData)
  // 重新整理两侧树中数据
  leftData.value = sortData(originalLeft, ids, 'left')
  rightData.value = sortData(originalRight, ids, 'right')
  nextTick(() => {
    reLoad.value = true
  })
  checkVal()
}
/**
 * 将tree中的整理进行整理，判断数据是否再tree中显示
 * @param data tree数据
 * @param condition 被选中的数据
 * @param leftRight 整理左侧tree中的数据还是整理右侧tree中的数据
 */
const sortData = (data: any, condition: Array<string>, leftRight: string) => {
  if (leftRight === 'left') {
    const result = [];
    for (const item of data) {
      // 判断item的id是否在condition中，如果不在，说明不需要删除
      if (!condition.includes(item.id)) {
        // 如果item有children属性，递归调用本函数，传入item的children和condition
        if (item.children) {
          item.children = sortData(item.children, condition, leftRight);
        }
        // 如果item的children为空数组，删除item的children属性
        if (item.children && item.children.length === 0) {
          delete item.children;
        }
        result.push(item);
      }
    }
    return result;
  } else {
    const result = [];
    for (const item of data) {
      // 如果item的id在condition中，说明该数据需要保留
      if (condition.includes(item.id)) {
        result.push(item);
      } else {
        // 否则，判断item是否有children属性
        if (item.children) {
          const subResult = sortData(item.children, condition, leftRight);
          // 如果返回的结果数组不为空，说明有符合条件的子数据
          if (subResult.length > 0) {
            // 将item的children属性更新为返回的结果数组
            item.children = subResult;
            result.push(item);
          }
        }
      }
    }
    return result;
  }
}
/**
 * 如果新数组中的id再旧数组中存在则删除原始数组中的id
 * @param oldIds 原始id
 * @param newIds 新id
 */
const removeId = (data: any, newIds: Array<string>) => {
  const ids = []
  for (const item of data) {
    if (!newIds.includes(item.id)) {
      ids.push(item)
    }
  }
  return ids
}
/**
 * 将id从备选中的数据取出
 * @param arr tree中被选中的数据
 */
const extractId = (arr: any) => {
  const newArr = []
  for (const i in arr) {
    newArr.push(arr[i].id)
  }
  return newArr
}
//返回父组件
const checkVal = () => {
  emit('checkVal', toData.value)
}

onMounted(() => {
  initData();
})

defineExpose({
  /**
   * 清空数据
   */
  clearData() {
    toData.value = [];
    rightData.value = [];
  },
  initData
});
</script>
<style>
.tree-transfer {
  width: 100%;
  display: flex;
  justify-content: space-between;

  .left-tree, .right-tree {
    flex-grow: 1;
    width: calc((100% - 60px) / 2);

    .tree-tit {
      margin-bottom: 10px;
    }

    .list {
      overflow: auto;
      height: 300px;
      border: 1px solid #ddd;
      border-radius: 4px;

      .item {
        padding: 0 10px;
        font-size: 14px;
        line-height: 26px;
        cursor: pointer;

        &.active {
          background: #b9d7fa;
        }
      }

      .item-checkbox {
        height: 26px;
        padding: 0 10px;
        font-size: 14px;
        line-height: 26px;

        & > .el-checkbox {
          height: 26px;
        }
      }
    }
  }

  .btn-div {
    width: 120px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .el-checkbox__input.is-disabled .el-checkbox__inner {
    display: none;
  }
}
</style>

